/**
 * 
 */
package jp.tkym.labs.store.sql;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jp.tkym.labs.store.ormap.OrMapElement;
import jp.tkym.labs.store.ormap.OrMapObject;
import jp.tkym.labs.store.ormap.OrMapObjectProvider;
import jp.tkym.labs.util.prop.PropertyResolver;
import jp.tkym.labs.util.prop.PropertyResolverProvider;

class PreparedStatementBinder{
	
	void bindValues(PreparedStatement ps, Object... values) throws SQLException{
		PreparedStatementBindSet bindSet = new PreparedStatementBindSet();
		for(Object value : values){
			Class<?> clsType = value.getClass();
			bindSet.put(clsType, value);
		} 
		bindSet.set(ps);
	}
	
	<T> void bindBean(PreparedStatementType type, PreparedStatement ps, T bean) throws SQLException{
		Class<T> cls = classOf(bean);
		PropertyListGenerator<T> generator = new PropertyListGenerator<T>(cls); 
		BindSetForBean<T> bindSet = new BindSetForBean<T>(cls);
		if(type==PreparedStatementType.INSERT){
			bindSet.addAllBindProperty(generator.allProperties());
		}
		if(type==PreparedStatementType.UPDATE){
			bindSet.addAllBindProperty(generator.excludeIdProperties());
			bindSet.addAllBindProperty(generator.idProperties());
		}
		if(type==PreparedStatementType.DELETE){
			bindSet.addAllBindProperty(generator.idProperties());
		}
		bindSet.doBind(bean, ps);
	}
	
	@SuppressWarnings("unchecked")
	<T> Class<T> classOf(T entity){
		return (Class<T>) entity.getClass();
	}
	
	private class PropertyListGenerator<T>{
		private OrMapObject orTarget;
		public PropertyListGenerator(Class<T> cls) {
			orTarget = OrMapObjectProvider.getInstance().get(cls);
		}
		List<String> idProperties(){
			List<String> propertyList = new ArrayList<String>(orTarget.elements().size());
			for(OrMapElement element : orTarget.ids()){
				if(element.isId())propertyList.add(element.getPropertyName());
			}
			return propertyList;
		}
		
		List<String> allProperties(){
			List<String> propertyList = new ArrayList<String>(orTarget.elements().size());
			for(OrMapElement element : orTarget.elements()){
				propertyList.add(element.getPropertyName());
			}
			return propertyList;
		}
		
		List<String> excludeIdProperties(){
			List<String> propertyList = new ArrayList<String>(orTarget.elements().size());
			for(OrMapElement element : orTarget.elements()){
				if(!element.isId())propertyList.add(element.getPropertyName());
			}
			return propertyList;
		}
	}
	
	private class BindSetForBean<T>{
		private PropertyResolver<T> propertyResolver;
		private List<String> bindProperties = new ArrayList<String>();
		private Map<String, String> orMap = new HashMap<String, String>();
		
		BindSetForBean(Class<T> cls){
			propertyResolver = PropertyResolverProvider.getInstance().get(cls);
			OrMapObject orMapObject = OrMapObjectProvider.getInstance().get(cls);
			for(OrMapElement element : orMapObject.elements()){
				orMap.put(element.getPropertyName(), element.getColumnName());
			}
		}
		
		void addAllBindProperty(Collection<String> properties){
			for(String property : properties)
				addBindProperty(property);
		}
		
		void addBindProperty(String property){
			bindProperties.add(property);
		}
		
		void doBind(T bean, PreparedStatement ps) throws SQLException{
			PreparedStatementBindSet bindSet = new PreparedStatementBindSet();
			for(String propertyName : bindProperties){
				Class<?> cls = propertyResolver.getTypeOf(propertyName);
				Object value = propertyResolver.get(bean, propertyName);
				bindSet.put(cls, value);
				bindSet.set(ps);
			}
		}
	}
	
	private class PreparedStatementBindSet{
		private static final int INITIAL_INDEX = 1;
		private List<Class<?>> classList = new ArrayList<Class<?>>();
		private List<Object> valueList = new ArrayList<Object>();
		private PreparedStatementBindHelper helper = new PreparedStatementBindHelper();
		
		void put(Class<?> cls, Object value){
			classList.add(cls);
			valueList.add(value);
		}
		
		void set(PreparedStatement ps) throws SQLException {
			for(int i=0; i<classList.size(); i++){
				Class<?> cls = classList.get(i);
				Object value = valueList.get(i);
				helper.set(i+INITIAL_INDEX, ps, cls, value);
			}
		}
	}
}